//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>

#include "vga.h"
#include "keyboard.h"

#include "ctrl.h"
#include "asciikey.h"
#include "consoledev.h"

#ifdef _DEBUG
#define Assert(x)   if (!((bool)(x))) { \
                        DebugBreak(); \
                    }
#else
#define Assert(x)
#endif

#define  READKEYBOARD  1

extern IDeviceDriver *g_pKbdDriver;
EXTERN_C void PrintComPort(const char *szMessage);

IDeviceDriver     *g_pConsoleDev = NULL;

EXTERN_C IDeviceDriver *g_pConsoleChannelObj;

EXTERN_C Boolean g_bUsingUSBConsole;

EXTERN_C Boolean g_bUsingConsoleChannel;

//
// The ConsoleDev driver's classfactory
//
EXTERN IDeviceDriver * CDECL CreateConsoleDev()
{
    ECode ec;

    g_pConsoleDev = new ConsoleDev;
    if (NULL == g_pConsoleDev) {
        Assert(0 && "There have no enough memory");
        return NULL;
    }

    // Initialize keyboard hardware
    ec = ((ConsoleDev*)g_pConsoleDev)->Initialize();
    if (FAILED(ec)) {
        delete g_pConsoleDev;
        g_pConsoleDev = NULL;
        return NULL;
    }

    g_pConsoleDev->AddRef();

    return g_pConsoleDev;
}

ECode ConsoleDev::Initialize()
{
    assert(NULL != g_pKbdDriver);
    m_pKbdDriver = g_pKbdDriver;

    m_pVGADriver = CreateVGA(0, NULL);
    if (NULL == m_pVGADriver) {
        m_pKbdDriver->Release();
        return E_FAIL;
    }

    MemoryBuf_<8> ezbLength;

    // Get the screen column
    ECode ec = m_pVGADriver->Control(2, NULL_MEMORYBUF, &ezbLength, NULL);
    if (NOERROR != ec) {
        m_pVGADriver->Release();
        m_pKbdDriver->Release();
        return ec;
    }

    m_uScreenColumn = *(uint32_t *)ezbLength.GetPayload();

    // Get the screen row
    ec = m_pVGADriver->Control(3, NULL_MEMORYBUF, &ezbLength, NULL);
    if (NOERROR != ec) {
        m_pVGADriver->Release();
        m_pKbdDriver->Release();
        return ec;
    }

    m_uScreenRow = *(uint32_t *)ezbLength.GetPayload();

    m_pWriteBuf = (char *)malloc((MAXLINELENGTH + 1) * sizeof(char));
    if (NULL == m_pWriteBuf) {
        m_pVGADriver->Release();
        m_pKbdDriver->Release();
        return E_OUT_OF_MEMORY;
    }

    // Create Screen buffer. now: 80 * (25 + 1);
    m_puScreenBuffer = (uint16_t *)DzAllocKernelPages(2);
    if (NULL == m_puScreenBuffer) {
        m_pVGADriver->Release();
        m_pKbdDriver->Release();
        return E_OUT_OF_MEMORY;
    }

    memset(m_puScreenBuffer, 0, m_uScreenColumn * (m_uScreenRow + 1) * 2);

    m_uCursorPosition = 0;
    m_uLastPosition = 0;
    m_bBrush = FALSE;

    return NOERROR;
}

ECode ConsoleDev::ReadKeyboard(uint8_t *p)
{
    ECode ec;
    int c = 0;
    KBDStatus keybdinfo;
    MemoryBuf ebbKeyBuff((Byte *)&keybdinfo, sizeof(KBDStatus));

    do {
        ec = m_pKbdDriver->Read(0, 1, &ebbKeyBuff, NULL);
        assert(NOERROR == ec);

        if (!keybdinfo.bKeyPress) {
            continue;
        }
        //Translate virtual key to Ascii
        c = TranslateVirtualcodeToASCII(keybdinfo.uVirKey,
                    keybdinfo.uLedStatus,
                    keybdinfo.uKbdStatus);
    } while (c == 0);
    *p = (uint8_t)c;

    return NOERROR;
}

ECode ConsoleDev::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    ECode ec;
    if (g_bUsingConsoleChannel) {
        if (g_pConsoleChannelObj != NULL) {
            g_pConsoleChannelObj->Read(0, bytesToRead, pBuffer, NULL);
        } // else do nothing
    }
    else {
        uint8_t *p = (uint8_t *)pBuffer->GetPayload();
        if (READKEYBOARD) {
            ec = ReadKeyboard(p);
            assert(NOERROR == ec);
        }
        else {
            BspReadComPort(COMPORT_PRINT, p);
        }
        pBuffer->SetUsed(1);
    }
    return NOERROR;
}

ECode ConsoleDev::WriteToScreen(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    char        *pwcString = (char *)buffer.GetPayload();
//    kprintf("the String is %s\n", pwcString);
    uint32_t    uLength = buffer.GetUsed();
    ECode     ec = NOERROR;

    if (0 == uLength || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    if ((UInt32)u64Offset < m_uScreenColumn * (m_uScreenRow + 1)) {
        m_uCursorPosition = (UInt32)u64Offset;
        m_uLastPosition = (UInt32)u64Offset;
    }

    // Write to the screen buffer
    for (uint32_t i = 0; i < uLength; i++, pwcString++) {
        switch (*pwcString) {
            case '\a':{
                BspBeep();
                break;
            }

            case '\b':{
                do {
                    if (0 == m_uLastPosition % m_uScreenColumn) break;

                    m_puScreenBuffer[--m_uLastPosition] = 0x20; // spacebar
                } while ((m_puScreenBuffer[m_uLastPosition - 1] & FLAG_TAB)
                    && (m_uLastPosition % 8));

                break;
            }

            case '\n':{
                m_uLastPosition += m_uScreenColumn;
                // no break statement! Need translate LF to CRLF.
            }

            case '\r':{
                m_uLastPosition -= m_uLastPosition % m_uScreenColumn;
                break;
            }

            case '\t':{
                do {
                    m_puScreenBuffer[m_uLastPosition++] = 0x20 | FLAG_TAB;
                } while (m_uLastPosition % 8);

                break;
            }

            default:
                m_puScreenBuffer[m_uLastPosition++] = *(wchar_t *)pwcString;
        }

        // Scroll
        if (m_uLastPosition >= (m_uScreenColumn * m_uScreenRow)) {
            memcpy(m_puScreenBuffer, m_puScreenBuffer + m_uScreenColumn,
                    sizeof(uint16_t) * (m_uScreenColumn * (m_uScreenRow - 1)));

            for (uint_t j = m_uScreenColumn * (m_uScreenRow - 1);
                j <= m_uScreenColumn * m_uScreenRow; j++) {
                m_puScreenBuffer[j] = 0x20;
            }

            m_uLastPosition -= m_uScreenColumn;
            m_uCursorPosition = m_uLastPosition;

            m_bBrush = TRUE;
        }
    }

    // Output to screen
    Int32 uWriteSize = 0;
    if (m_bBrush) {
        m_bBrush = FALSE;
        MemoryBuf ezbbScr((Byte *)m_puScreenBuffer,
                                (m_uScreenColumn * m_uScreenRow) * 2);

        ec = m_pVGADriver->Write(0, ezbbScr, &uWriteSize, NULL);
        Assert(NOERROR == ec);
    }
    else {
        uint32_t uStrLength = m_uLastPosition - m_uCursorPosition;

        if (0 != uStrLength
            && uStrLength < m_uScreenColumn * m_uScreenRow) {
            MemoryBuf ezbbScr((Byte *)&m_puScreenBuffer[m_uCursorPosition],
                                                            uStrLength * 2);
            ec = m_pVGADriver->Write(m_uCursorPosition, ezbbScr,
                                                        &uWriteSize, NULL);
            Assert(NOERROR == ec);
            Assert(0 != m_uLastPosition);
        }
        else {
            uStrLength = m_uCursorPosition - m_uLastPosition;

            if (0 != uStrLength
                && uStrLength < m_uScreenColumn * m_uScreenRow) {
                MemoryBuf ezbbScr((Byte *)&m_puScreenBuffer[m_uLastPosition],
                                uStrLength * 2);
                ec = m_pVGADriver->Write(m_uLastPosition, ezbbScr,
                                &uWriteSize, NULL);
                Assert(NOERROR == ec);
            }
        }
    }

    m_uCursorPosition = m_uLastPosition;

    MemoryBuf   ezbbbCursor((Byte *)&m_uCursorPosition, 4);
    ec = m_pVGADriver->Control(0, ezbbbCursor,NULL, NULL);

    if (pBytesWritten) {
        *pBytesWritten = uLength;
    }

    return NOERROR;
}

ECode ConsoleDev::WriteToCOM(const MemoryBuf & buffer)
{
    uint32_t len;
    char *p = (char *)buffer.GetPayload();
    uint32_t c = buffer.GetUsed();

    while (c > 0) {
        len = (c > MAXLINELENGTH) ? MAXLINELENGTH : c;
        for (uint32_t i = 0; i < len; i++) {
            m_pWriteBuf[i] = *p;
            p++;
        }
        m_pWriteBuf[len] = '\0';
        PrintComPort(m_pWriteBuf);
        c -= len;
    }

    return NOERROR;
}

ECode ConsoleDev::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;

    ec = WriteToCOM(buffer);
    assert(NOERROR == ec);
    ec = WriteToScreen(u64Offset, buffer, pBytesWritten,
                    ppCompletionEvent);
    assert(NOERROR == ec);

    return NOERROR;
}

ECode ConsoleDev::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec;

    if ((nControlCode < 0) || (nControlCode > 7)
        || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    switch (nControlCode) {
        case CTRL_SETCURSORPOS: { // Set the Cursor's position

            if (inBuffer.IsNullOrEmpty()) {
                return E_INVALID_ARGUMENT;
            }

            uint32_t uPosition = *(uint32_t *)inBuffer.GetPayload();

            if (uPosition >= m_uScreenColumn * m_uScreenRow) {
                return E_INVALID_ARGUMENT;
            }

            m_uCursorPosition = uPosition;
            m_uLastPosition = uPosition;

            ec = m_pVGADriver->Control(0, inBuffer,
                                    pOutBuffer, ppCompletionEvent);
            break;
        }

        case CTRL_GETCURSORPOS:  { // Get the Cursor's position
            if (pOutBuffer == NULL ||
                (uint32_t)pOutBuffer->GetCapacity() < sizeof(uint32_t)) {
                return E_INVALID_ARGUMENT;
            }

            *(uint32_t *)pOutBuffer->GetPayload() = m_uCursorPosition;
            pOutBuffer->SetUsed(sizeof(m_uCursorPosition));
            ec = NOERROR;

            break;
        }

        case CTRL_CLEARSCREEN: {  // Clear the screen
            memset(m_puScreenBuffer, 0, m_uScreenColumn * m_uScreenRow * 2);

            Int32 uWriteSize = 0;
            MemoryBuf ezbbScr((Byte *)m_puScreenBuffer,
                                (m_uScreenColumn * m_uScreenRow - 1) * 2);

            ec = m_pVGADriver->Write(0, ezbbScr, &uWriteSize, NULL);
            Assert(NOERROR == ec);
//        Assert((m_uScreenColumn * m_uScreenRow - 1) == uWriteSize);

            m_uCursorPosition = 0;
            m_uLastPosition = 0;

            MemoryBuf ezbbPos((Byte *)&m_uCursorPosition, 4);
            ec = m_pVGADriver->Control(0, ezbbPos, pOutBuffer,
                            ppCompletionEvent);
            Assert(NOERROR == ec);
            ec = NOERROR;

            break;
        }

        case CTRL_SETCURSORSHAPE: {  // Set the cursor shape
            ec = m_pVGADriver->Control(4, inBuffer,
                                pOutBuffer, ppCompletionEvent);
            break;
        }

        case CTRL_GETSCREENCOL: {  // Get the screen columns;
            if (pOutBuffer == NULL ||
                (uint32_t)pOutBuffer->GetCapacity() < sizeof(uint32_t)) {
                return E_INVALID_ARGUMENT;
            }

#if defined(_DEBUG)
            ec = m_pVGADriver->Control(2, inBuffer,
                                    pOutBuffer, ppCompletionEvent);

            Assert(NOERROR == ec);
            Assert((*(uint32_t *)pOutBuffer->GetPayload()) == m_uScreenColumn);
#endif

            *(uint32_t *)pOutBuffer->GetPayload() = m_uScreenColumn;
            pOutBuffer->SetUsed(sizeof(uint32_t));

            ec = NOERROR;

            break;
        }

        case CTRL_GETSCREENROW: {  // Get the screen rows;
            if (pOutBuffer == NULL ||
                (uint32_t)pOutBuffer->GetCapacity() < sizeof(uint32_t)) {
                return E_INVALID_ARGUMENT;
            }

#if defined(_DEBUG)
            ec = m_pVGADriver->Control(3, inBuffer,
                                    pOutBuffer, ppCompletionEvent);

            Assert(NOERROR == ec);
            Assert((*(uint32_t *)pOutBuffer->GetPayload()) == m_uScreenRow);
#endif

            *(uint32_t *)pOutBuffer->GetPayload() = m_uScreenRow;
            pOutBuffer->SetUsed(sizeof(uint32_t));
            ec = NOERROR;

            break;
        }

        case CTRL_CONFIGKEYBOARD: {
            ec = m_pKbdDriver->Control(4, inBuffer, pOutBuffer,
                            ppCompletionEvent);
            break;
        }

        case CTRL_MOVECURSOR:  { //move the cursor forward
            if (inBuffer.GetUsed() < (Int32)sizeof(uint32_t)) {
                return E_INVALID_ARGUMENT;
            }

           uint32_t uMove = *(uint32_t*)inBuffer.GetPayload();
           if (uMove > m_uCursorPosition) {
                return E_INVALID_ARGUMENT;
            }

            m_uCursorPosition = m_uCursorPosition - uMove;
            m_uLastPosition = m_uCursorPosition;

            //move the comcons cursor forward
            if (uMove > MAXLINELENGTH) {
                uMove = MAXLINELENGTH;
            }
            for (uint32_t i = 0; i < uMove; i++) {
                m_pWriteBuf[i] = '\b';
            }
            m_pWriteBuf[uMove] = '\0';
            PrintComPort(m_pWriteBuf);

            //move the screen cursor forward
            MemoryBuf ebbPos((Byte *)&m_uCursorPosition, 4);

            ec = m_pVGADriver->Control(0, ebbPos,
                            pOutBuffer, ppCompletionEvent);
            break;
        }

        default:
            ec = E_NOT_IMPLEMENTED;
    }

    return ec;
}
